/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file pyhp_layer.c
 * Python interpreter calls
 */


#include <libgen.h>

#include <httpd.h>
#include <http_log.h>
#include <apr_strings.h>

#include "pyhp_layer.h"
#include "logging.h"
#include "parse.h"
#include "db.h"

extern char **environ;
extern module **ap_loaded_modules;


/**
 * Retrieve the __internals dict from the pyhp layer, we use it to store
 * private internal data of mod_pyhp needed to communicate between apache
 * and python layers
 */
static PyObject *pyhp_layer_get_internals(PyHPData *pyhp)
{
    PyObject *dict = PyModule_GetDict(pyhp->pyhp_layer);
    PyObject *self = PyDict_GetItemString(dict, "__internals");

    return self;
}

/**
 * Stores a value inside the internals of the module, it will be exposed to
 * the python layer as __internals and will be visible to the apache module
 * with pyhp_layer_get_internal
 *
 * @param key The name of the value to store
 * @param what The value to store
 */
void pyhp_layer_set_internal(PyHPData *pyhp, char const *key, void *what)
{
    PyObject *self = pyhp_layer_get_internals(pyhp);
    PyObject *req = PyCObject_FromVoidPtr(what, NULL);

    PyDict_SetItemString(self, key, req);

    Py_XDECREF(req);
}

/**
 * Retrieves a value inside the internals of the module
 *
 * @param key The name of the value to retrieve
 *
 * @return The stored value
 */
void *pyhp_layer_get_internal(PyHPData *pyhp, char const *key)
{
    PyObject *self = pyhp_layer_get_internals(pyhp);
    PyObject *item = PyDict_GetItemString (self, key);

    return PyCObject_AsVoidPtr(item);
}

static PyObject *pyhp_include(PyObject *self, PyObject *args)
{
    PyObject *text = NULL;

    if (!PyArg_ParseTuple(args, "O", &text))
    	return NULL;

    PyObject *tmp = PyDict_GetItemString(self, "apache_request");
    request_rec *r = PyCObject_AsVoidPtr(tmp);

    tmp = PyDict_GetItemString(self, "pydata");
    PyHPData *pydata = PyCObject_AsVoidPtr(tmp);

    char *filename = PyString_AsString(text);
    char *code = pyhp_parse_file(pydata, r, filename);

    PyHP_RunPython(pydata, r, code);

    Py_INCREF(Py_None);
    return Py_None;
}

/**
 * Exposes the write method to write output to the browser
 */
static PyObject *pyhp_write(PyObject *self, PyObject *args)
{
    PyObject *text = NULL;

    if (!PyArg_ParseTuple(args, "O", &text))
    	return NULL;

    PyObject *req = PyDict_GetItemString(self, "apache_request");
    request_rec *r = PyCObject_AsVoidPtr(req);
    char *txt;
    Py_ssize_t txt_size;

    PyString_AsStringAndSize(text, &txt, &txt_size);
    if ( (!r) || ((r->connection->aborted)) )
        PyErr_Format(PyExc_IOError, "Apache Request Lost");
    else
        ap_rwrite(txt, txt_size, r);

    Py_INCREF(Py_None);

    return Py_None;
}

/**
 * Exposes various informations about pyhp as a dictionary
 */
static PyObject *pyhp_info(PyObject *self, PyObject *args)
{
    char tmp[128];
    char **env;
    char *buffer = "";
    int n;

    PyObject *pyreq  = PyDict_GetItemString (self, "apache_request");
    request_rec *r = PyCObject_AsVoidPtr(pyreq);
    pyhp_config *conf = pyhp_get_config(r);

    PyObject *info = PyDict_New();

    pyhp_pydict_add_string(info, "HOSTNAME", r->hostname);
    pyhp_pydict_add_string(info, "AUTH_NAME", r->user);
    pyhp_pydict_add_string(info, "AUTH_TYPE", r->ap_auth_type);
    pyhp_pydict_add_string(info, "REQUEST_URI", (r->prev ? r->prev->unparsed_uri : r->unparsed_uri));
    pyhp_pydict_add_string(info, "URI", r->uri);
    pyhp_pydict_add_string(info, "FILENAME", r->filename);
    pyhp_pydict_add_string(info, "PATH", dirname(r->filename));
    pyhp_pydict_add_string(info, "CANONICAL_FILENAME", r->canonical_filename);
    pyhp_pydict_add_string(info, "QUERY_ARGS", r->args);

    pyhp_pydict_add_string(info, "PROTOCOL", r->protocol);
    pyhp_pydict_add_string(info, "CONTENT_TYPE", r->content_type);
    pyhp_pydict_add_string(info, "APACHE_HANDLER", r->handler);

    pyhp_pydict_add_string(info, "SERVER_ADMIN", r->server->server_admin);
    pyhp_pydict_add_string(info, "SERVER_HOSTNAME", r->server->server_hostname);
    pyhp_pydict_add_string(info, "SERVER_ADMIN", r->server->server_admin);

    pyhp_pydict_add_string(info, "PyHPMode", conf->mode);
    pyhp_pydict_add_string(info, "PyHPLogLevel", pyhp_loglevel_to_string(conf->log_level, tmp, 128));
    pyhp_pydict_add_string(info, "PyHPLog", conf->log);
    pyhp_pydict_add_string(info, "PyHPTempPath", conf->tmp_path);

    pyhp_pydict_add_string(info, "PyHPSessionBackend", pyhp_sessionbackend_to_string(conf->session_backend,
                                                                                     tmp, 128));
    pyhp_pydict_add_string(info, "PyHPSessionPath", conf->session_path);
    pyhp_pydict_add_string(info, "PyHPSessionTimeout", pyhp_sessiontimeout_to_string(conf->session_timeout,
                                                                                     tmp, 128));
    pyhp_pydict_add_string(info, "PyHPSessionName", conf->session_name);
    pyhp_pydict_add_string(info, "PyHPSessionDbUri", conf->session_db_uri);
    pyhp_pydict_add_string(info, "PyHPSessionDbUser", conf->session_db_user);
    pyhp_pydict_add_string(info, "PyHPSessionDbPass", conf->session_db_pass);

    PyObject *e = PyDict_New();
    for (env = environ; env != NULL && *env != NULL; env++) {
        char *tmp2;
        char *tmp1 = strdup(*env);

        if (!(tmp2 = strchr(tmp1, '='))) {
            free(tmp1);
            continue;
        }

        *tmp2 = 0;
        tmp2++;
        pyhp_pydict_add_string(e, tmp1, tmp2);
        free(tmp1);
    }
    PyDict_SetItemString(info, "ENV", e);
    Py_XDECREF(e);

    buffer = "";
    for (n = 0; ap_loaded_modules[n]; ++n)
        buffer = apr_pstrcat(r->pool, buffer, ap_loaded_modules[n]->name, " ", NULL);
    pyhp_pydict_add_string(info, "APACHE_MODULES", buffer);

    return info;
}

/**
 * PyHP methods table, there are the methods exposed inside the pyhp object
 */
static PyMethodDef method_table [] = {
    { "write", ( PyCFunction )pyhp_write, METH_VARARGS,
                                          "Write data to Apache stdout" },
    { "include", (PyCFunction)pyhp_include, METH_VARARGS,
                                          "Include a pyhp file" },
    { "info", (PyCFunction)pyhp_info, METH_VARARGS,
                                          "Returns a dict with various informations about pyhp" },
    { NULL, NULL, 0, NULL }
    };

/**
 * Initializes the PyHP Layer creating the python module and setting up the available methods
 */
PyObject *pyhp_layer_init()
{
    PyObject *dict;
    PyObject *self = PyDict_New();

    PyObject *ret = Py_InitModule4("mod_pyhp", method_table, "", self,
                                   PYTHON_API_VERSION);
    dict = PyModule_GetDict(ret);
    PyDict_SetItemString(dict, "__internals", self);

    pyhp_logging_export(dict, self);
    pyhp_cookies_export(dict);
    pyhp_session_export(dict, self);
    pyhp_db_export(dict, self);

    return ret;
}

/**
 * Creates the global variables and the execution environment
 */
PyObject *pyhp_layer_create_globals(PyObject *layer)
{
    PyObject *main_mod = PyImport_AddModule("__main__");
    PyObject *dict = PyModule_GetDict(main_mod);

    PyDict_SetItemString(dict, "pyhp", layer);

    return dict;
}

/**
 * Adds a subobject to the python layer.
 * Subobjects are the children of the pyhp object
 */
void pyhp_layer_set_object(PyHPData *pyhp, const char *key, PyObject *val)
{
    PyObject *dict = PyModule_GetDict(pyhp->pyhp_layer);
    PyDict_SetItemString(dict, key, val);
    Py_XDECREF(val);
}

/**
 * Retrieves a subobject from the python layer
 */
PyObject *pyhp_layer_get_object(PyHPData * pyhp, const char * key)
{
    PyObject *dict = PyModule_GetDict(pyhp->pyhp_layer);
    return PyDict_GetItemString(dict, key);
}

/**
 * Pickles the given object and returns the resulting string
 */
char * pyhp_layer_pack_object(PyHPData *pydata, PyObject * cookie)
{
    PyObject *m = pydata->pickle_module;
    PyObject *r = NULL;

    if(m) {
        PyObject *d = PyModule_GetDict(m);
        PyObject *o = PyDict_GetItemString(d, "dumps");
        if (o) {
            PyObject *args = Py_BuildValue("(Oi)", cookie, 0);

            r = PyEval_CallObject(o, args);

            Py_DECREF(args);
        }
    }

    if (!r)
        return "";
    else
        return PyString_AsString(r);
}

/**
 * Unpickles the given string and returns the resulting object
 */
PyObject *pyhp_layer_unpack_object(PyHPData *pydata, char * content)
{
    PyObject *m = pydata->pickle_module;
    PyObject *r = NULL;

    if(m) {
        PyObject *d = PyModule_GetDict(m);
        PyObject *o = PyDict_GetItemString(d, "loads");
        if (o) {
            PyObject *args = Py_BuildValue("(s)", content);
            r = PyEval_CallObject(o, args);
            Py_XDECREF(args);
        }
    }

    return r;
}
